/**
 * This file is part of the NatCrackerToolbox.
 * 
 * Copyright (C) 2009 - 2011 Peerialism Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 **/
package se.peerialism.natcrackeremu.model.traversal.logic;

import java.io.FileWriter;
import java.io.IOException;
import java.util.HashSet;
import java.util.StringTokenizer;


import com.peerialism.natcracker.common.FilteringPolicy;
import com.peerialism.natcracker.common.GatewayDevice;
import com.peerialism.natcracker.common.PortMappingTechnique;
import com.peerialism.natcracker.common.TraversalStrategy;
import com.peerialism.natcracker.common.GatewayDevice.GatewayType;
import com.peerialism.natcracker.common.PortAllocationPolicy;
import com.peerialism.natcracker.common.PortMappingPolicy;

public class TraversalLogicV1 {
	static boolean ENABLE_OPTIMIZATION = true;

	enum ReqKn {
		NoNeed, NeedE, ENotChange
	}

	public static TraversalStrategy getStrategy(GatewayDevice x1, GatewayDevice x2) {
		PortMappingTechnique str1 = PortMappingTechnique.NONE;
		PortMappingTechnique str2 = PortMappingTechnique.NONE;
		boolean cantConnect = true;
		boolean first = false;
		boolean second = false;
		if ((first = (x1.getPortFilteringPolicy() == FilteringPolicy.ENDPOINT_INDEPENDENT && x1.getPortMappingPolicy() == PortMappingPolicy.ENDPOINT_INDEPENDENT))
				|| (second = (x2.getPortFilteringPolicy() == FilteringPolicy.ENDPOINT_INDEPENDENT && x2.getPortMappingPolicy() == PortMappingPolicy.ENDPOINT_INDEPENDENT))) {
			// One is F=EI && M=EI
			if (first)
				str1 = PortMappingTechnique.ACQUIRE;
			else if (second)
				str2 = PortMappingTechnique.ACQUIRE;
			cantConnect = false;
		} else if (!(x1.getPortAllocationPolicy() == PortAllocationPolicy.RANDOM && x2.getPortAllocationPolicy() == PortAllocationPolicy.RANDOM)) {
			ReqKn reqFlt1 = checkFiltering(x1);
			ReqKn reqFlt2 = checkFiltering(x2);
			// System.out.println("Flt1 :" + reqFlt1);
			// System.out.println("Flt2 :" + reqFlt2);
			// ReqKn reqMap1 = checkMapping(x1);
			// ReqKn reqMap2 = checkMapping(x2);
			// System.out.println("Map1 :" + reqMap1);
			// System.out.println("Map2 :" + reqMap2);
			// ReqKn req1 = checkTravPreCond(x1, ENABLE_SPAWNING);
			// ReqKn req2 = checkTravPreCond(x2, ENABLE_SPAWNING);
			if (satReq(x2, reqFlt1) && satReq(x1, reqFlt2)) {
				str1 = k2s(x1);
				str2 = k2s(x2);
				if (ENABLE_OPTIMIZATION) {
					boolean less1 = lessThanPD(x1);
					boolean less2 = lessThanPD(x2);
					if (less1 && less2) {
						if (str1 == PortMappingTechnique.PREDICT_PRESERVATION) {
							if (str2 == PortMappingTechnique.PREDICT_CONTIGUITY || str2 == PortMappingTechnique.PREDICT_PRESERVATION)
								str2 = PortMappingTechnique.NONE;
						} else if (str1 == PortMappingTechnique.PREDICT_CONTIGUITY)
							if (str2 == PortMappingTechnique.PREDICT_CONTIGUITY)
								str2 = PortMappingTechnique.NONE;
							else if (str2 == PortMappingTechnique.PREDICT_PRESERVATION)
								str1 = PortMappingTechnique.NONE;
					} else if (!less1 && less2) {
						if (str2 != PortMappingTechnique.NONE)
							str1 = PortMappingTechnique.NONE;
					} else if (less1 && !less2)
						if (str1 != PortMappingTechnique.NONE)
							str2 = PortMappingTechnique.NONE;
				}
				cantConnect = false;
			}
		} else if (x1.getPortFilteringPolicy() == FilteringPolicy.ENDPOINT_INDEPENDENT && x2.getPortFilteringPolicy() != FilteringPolicy.PORT_DEPENDENT) {
			str1 = PortMappingTechnique.ACQUIRE;
			cantConnect = false;
		} else if (x2.getPortFilteringPolicy() == FilteringPolicy.ENDPOINT_INDEPENDENT && x1.getPortFilteringPolicy() != FilteringPolicy.PORT_DEPENDENT) {
			str2 = PortMappingTechnique.ACQUIRE;
			cantConnect = false;
		}
		TraversalStrategy result;
		if (cantConnect)
			result = new TraversalStrategy(!cantConnect);
		else
			result = new TraversalStrategy(str1, str2, 0.0);
		return result;
	}

	private static boolean lessThanPD(GatewayDevice x) {
		if (x.getPortMappingPolicy() != PortMappingPolicy.PORT_DEPENDENT && x.getPortFilteringPolicy() != FilteringPolicy.PORT_DEPENDENT)
			return true;
		return false;
	}

	// private static ReqKn checkMapping(GatewayDevice x) {
	// ReqKn ret = ReqKn.NoNeed;
	// PortMappingPolicy map = x.getPortMappingPolicy();
	// PortAllocationPolicy alloc = x.getPortAllocationPolicy();
	//
	// if (alloc == PortAllocationPolicy.PORT_PRESERVATION) { /* Avoid clashes */
	// if (map == PortMappingPolicy.PORT_DEPENDENT)
	// ret = ReqKn.ENotChange;
	// } else {
	// if (map == PortMappingPolicy.PORT_DEPENDENT) {
	// ret = ReqKn.ENotChange;
	// }
	// }
	//
	// return ret;
	// }
	private static ReqKn checkFiltering(GatewayDevice x) {
		ReqKn ret = ReqKn.NoNeed;
		FilteringPolicy flt = x.getPortFilteringPolicy();
		PortAllocationPolicy alloc = x.getPortAllocationPolicy();
		if (flt == FilteringPolicy.PORT_DEPENDENT)
			if (alloc == PortAllocationPolicy.RANDOM)
				ret = ReqKn.ENotChange;
			else
				ret = ReqKn.NeedE;
		return ret;
	}

	// private static boolean satReq(GatewayDevice x, ReqKn reqFlt, ReqKn reqMap) {
	//
	// PortAllocationPolicy alloc = x.getPortAllocationPolicy();
	// PortMappingPolicy map = x.getPortMappingPolicy();
	//
	// if (reqFlt == ReqKn.NeedE || reqMap == ReqKn.NeedE) {
	// return alloc != PortAllocationPolicy.RANDOM;
	// }
	//
	// if (reqMap == ReqKn.ENotChange) {
	// return !(map == PortMappingPolicy.PORT_DEPENDENT && alloc == PortAllocationPolicy.RANDOM);
	// }
	//
	// if (reqFlt == ReqKn.ENotChange) {
	// return (map != PortMappingPolicy.PORT_DEPENDENT && alloc != PortAllocationPolicy.RANDOM);
	// }
	//
	// return true;
	// }
	private static boolean satReq(GatewayDevice x, ReqKn reqFlt) {
		PortAllocationPolicy alloc = x.getPortAllocationPolicy();
		PortMappingPolicy map = x.getPortMappingPolicy();
		if (reqFlt == ReqKn.NeedE)
			return alloc != PortAllocationPolicy.RANDOM;
		// if (reqMap == ReqKn.ENotChange) {
		// return !(map == PortMappingPolicy.PORT_DEPENDENT && alloc == PortAllocationPolicy.RANDOM);
		// }
		//
		if (reqFlt == ReqKn.ENotChange)
			return (map != PortMappingPolicy.PORT_DEPENDENT);
		return true;
	}

	// private static ReqKn checkTravPreCond(GatewayDevice x, boolean enableSpawn) {
	//
	// if (enableSpawn) {
	// return checkTravPreCondNotRestrictive(x);
	// } else {
	// return checkTravPreCondRestrictive(x);
	// }
	//
	// }
	// private static ReqKn checkTravPreCondRestrictive(GatewayDevice x) {
	//
	// ReqKn ret = ReqKn.NoNeed;
	//
	// FilteringPolicy flt = x.getPortFilteringPolicy();
	// PortMappingPolicy map = x.getPortMappingPolicy();
	//
	// if (flt == FilteringPolicy.PORT_DEPENDENT || (map == PortMappingPolicy.PORT_DEPENDENT)) {
	// ret = ReqKn.NeedE;
	// }
	// return ret;
	// }
	//
	// private static ReqKn checkTravPreCondNotRestrictive(GatewayDevice x) {
	//
	// ReqKn ret = null;
	//
	// FilteringPolicy flt = x.getPortFilteringPolicy();
	// PortMappingPolicy map = x.getPortMappingPolicy();
	// PortAllocationPolicy alloc = x.getPortAllocationPolicy();
	//
	// if (flt == FilteringPolicy.PORT_DEPENDENT || (map == PortMappingPolicy.PORT_DEPENDENT && alloc == PortAllocationPolicy.PORT_PRESERVATION)) {
	// ret = ReqKn.NeedE;
	// }
	// return ret;
	// }
	// private static boolean satReq(GatewayDevice x, ReqKn req, boolean enableSpawning) {
	//
	// if (enableSpawning) {
	// return satReqSpawn(x, req);
	// } else {
	// return satReqRestrictive(x, req);
	// }
	//
	// }
	// private static boolean satReqSpawn(GatewayDevice x, ReqKn req) {
	// if (req == ReqKn.NoNeed || req == ReqKn.NeedH) {
	// return true;
	// } else if (req == ReqKn.NeedE && x.getPortAllocationPolicy() != PortAllocationPolicy.RANDOM) {
	// return true;
	// } else
	//
	// if (req == ReqKn.NeedAdapt
	// && ((x.getPortFilteringPolicy() != FilteringPolicy.PORT_DEPENDENT && x.getPortMappingPolicy() != PortMappingPolicy.PORT_DEPENDENT) ||
	// x.getPortAllocationPolicy() != PortAllocationPolicy.RANDOM)) {
	// // Either it can adapt or its endpoint is known
	// return true;
	// }
	// return false;
	// }
	// private static boolean satReqRestrictive(GatewayDevice x, ReqKn req) {
	// if (req == ReqKn.NoNeed) {
	// return true;
	// } else if (req == ReqKn.NeedE && x.getPortAllocationPolicy() != PortAllocationPolicy.RANDOM) {
	// return true;
	// }
	//
	// // if (req == ReqKn.NeedAdapt
	// // && ((x.getPortFilteringPolicy() != FilteringPolicy.PORT_DEPENDENT && x.getPortMappingPolicy() != PortMappingPolicy.PORT_DEPENDENT) ||
	// // x.getPortAllocationPolicy() != PortAllocationPolicy.RANDOM)) {
	// // // Either it can adapt or its endpoint is known
	// // return true;
	// // }
	// return false;
	// }
	private static PortMappingTechnique k2s(GatewayDevice x) {
		PortMappingTechnique str = PortMappingTechnique.NONE;
		if (x.getPortAllocationPolicy() == PortAllocationPolicy.PORT_CONTIGUITY)
			str = PortMappingTechnique.PREDICT_CONTIGUITY;
		else if (x.getPortAllocationPolicy() == PortAllocationPolicy.PORT_PRESERVATION)
			str = PortMappingTechnique.PREDICT_PRESERVATION;
		return str;
	}

	public static void main(String[] args) {
		ENABLE_OPTIMIZATION = true;
		GatewayDevice x1 = new GatewayDevice();
		x1.setGatewayType(GatewayType.NAT);
		GatewayDevice x2 = new GatewayDevice();
		x2.setGatewayType(GatewayType.NAT);
		PortAllocationPolicy[] mts = PortAllocationPolicy.values();
		FilteringPolicy[] fps = FilteringPolicy.values();
		GatewayDevice[] gws = new GatewayDevice[mts.length * fps.length * PortMappingPolicy.values().length];
		int i = 0;
		for (PortAllocationPolicy mt : mts)
			for (FilteringPolicy fp : fps)
				for (PortMappingPolicy m : PortMappingPolicy.values()) {
					GatewayDevice gatewayDevice1 = new GatewayDevice();
					gatewayDevice1.setPortMappingPolicy(m);
					gatewayDevice1.setPortFilteringPolicy(fp);
					gatewayDevice1.setPortAllocationPolicy(mt);
					gatewayDevice1.setGatewayType(GatewayType.NAT);
					gws[i] = gatewayDevice1;
					i++;
				}
		FileWriter f;
		try {
			f = new FileWriter("natBig.html");
			f.write("<TABLE border='1'>");
			f.write("<TR>");
			f.write("<TD>--</TD><TD>A</TD><TD></TD>");
			for (GatewayDevice gw : gws)
			{
				f.write("<TD><B>");
				f.write(removeDash(GatewayDevice.shorten(gw.getPortAllocationPolicy().name(), "_")) + "");
				f.write("</TD></B>");
			}
			f.write("</TR>");
			f.write("<TR>");
			f.write("<TD>B</TD><TD></TD><TD></TD>");
			for (GatewayDevice gw : gws)
			{
				f.write("<TD><B>");
				f.write(removeDash(GatewayDevice.shorten(gw.getPortFilteringPolicy().name(), "_")) + "");
				f.write("</TD></B>");
			}
			f.write("</TR>");
			f.write("<TR>");
			f.write("<TD></TD><TD></TD><TD></TD>");
			for (GatewayDevice gw : gws)
			{
				f.write("<TD><B>");
				f.write(removeDash(GatewayDevice.shorten(gw.getPortMappingPolicy().name(), "_")) + "");
				f.write("</TD></B>");
			}
			f.write("</TR>");
			HashSet<String> done = new HashSet<String>();
			for (int r = 0; r < gws.length; r++) {
				f.write("<TR>");
				// f.write("<TD></TD><TD></TD>");
				f.write("<TD><B>" + removeDash(GatewayDevice.shorten(gws[r].getPortAllocationPolicy().name(), "_")) + "</B></TD>");
				f.write("<TD><B>" + removeDash(GatewayDevice.shorten(gws[r].getPortFilteringPolicy().name(), "_")) + "</B></TD>");
				f.write("<TD><B>" + removeDash(GatewayDevice.shorten(gws[r].getPortMappingPolicy().name(), "_")) + "</B></TD>");
				for (int c = 0; c < gws.length; c++) {
					String cur = (c <= r) ? r + "-" + c : c + "-" + r;
					if (!done.contains(cur))
						done.add(cur);
					else {
						f.write("<TD/>");
						continue;
					}
					TraversalStrategy s = getStrategy(gws[r], gws[c]);
					// TraversalStrategy olds = ConnectivityTable.getStrategy(gws[r], gws[c]);
					String color = s.canConnect() ? "green" : "red";
					// String colorOld = olds.canConnect() ? "green" : "red";
					String entry = (s.canConnect() ? fromRealToModelStr(removeDash(GatewayDevice.shorten(s.getDownloaderStrategy().name(), "_"))) : "N") + "-"
					+ (s.canConnect() ? fromRealToModelStr(removeDash(GatewayDevice.shorten(s.getUploaderStrategy().name(), "_"))) : "N");
					f.write("<TD bgcolor='" + bgColor(entry) + "'><font color='" + color + "'>");
					f.write("" + entry + "</font><BR/>");
					f.write("</TD>");
				}
				f.write("</TR>");
			}
			f.write("<TABLE>");
			f.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public static String bgColor(String str) {
		if (str.equals("G-H") || str.equals("H-G"))
			return "lightgreen";
		if (str.contains("P-G") || str.contains("G-P"))
			return "yellow";
		if (str.contains("G-C") || str.contains("C-G"))
			return "cyan";
		if (str.contains("P-P"))
			return "red";
		if (str.contains("C-C"))
			return "blue";
		if (str.contains("C-P") || str.contains("P-C"))
			return "magenta";
		return "";
	}

	public static String fromRealToModelStr(String str) {
		if (str.contains("N"))
			return "G";
		if (str.contains("A"))
			return "H";
		if (str.contains("PP"))
			return "P";
		if (str.contains("PC"))
			return "C";
		return "";
	}

	public static String removeDash(String str) {
		StringTokenizer tok = new StringTokenizer(str, "_");
		if (tok.countTokens() == 2)
			return tok.nextToken() + tok.nextToken();
		return str;
	}
}
